/***************************************************************************************
 *
 *  Ports all the way down - patwd
 *  ------------------------------
 *
 *  Version 0.3.0
 *  Jan 2012
 *  Copyright (C) 2012 by Sebastian Roschke (bykhee@gmail.com)
 *
 *  patwd is a tiny deamon to obfuscate open ports on a system by listening to
 *  network traffic, filtering all SYN requests for certain IPs, and responding
 *  with corresponding ACK messages for a specified number of ports, either static
 *  or dynamic. 
 *
 *  Distributed under the terms and conditions of GNU LGPL.
 *
 ***************************************************************************************/

#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#include "debug.h"
#include "pcap_foo.h"
#include "os.h"

#include "alloc-inl.h"
#include "types.h"

#define MAX_DEV_SIZE 128

#define VERSION "v0.3.1"

#ifdef DEBUG_BUILD
struct TRK_obj* TRK[ALLOC_BUCKETS];
u32 TRK_cnt[ALLOC_BUCKETS];
#endif /* DEBUG_BUILD */

u8 ports[NUM_PORTS];
u16 p_cnt = 0;

static struct option long_options[] = {
  {"mode", required_argument, 0, 'm' },
  {"test", no_argument, 0, 't'},
  {"interface", required_argument, 0, 'i'},
  {"source", required_argument, 0, 's'},
  {"port", required_argument, 0, 'p'},
  {"verbose", required_argument, 0, 'v'},
  {"dynamic", no_argument, 0, 'd'},
  {"version", no_argument, 0, 0},
  {"help", no_argument, 0, 'h'},
  {0, 0, 0, 0 }
};

void print_header(u8* dev, u8* mode, u8** ips, u16 ips_cnt, u8 ports[65535]) {

  printf("== \e[0;32mpatwd\e[m ==\n# by \e[0;34mbykhe\e[m\n# " VERSION "\n++++++++++++++++++++++++++++\n"
    "|  interface: \e[0;34m%s\e[m\n"
    "|  mode: \e[0;34m%s\e[m\n",
    dev, mode);

  printf("|\n"
    "|  ips (%u):\n", ips_cnt);

  int i = 0;
  for (i = 0; i < ips_cnt; i++) {
    printf("|    * \e[0;34m%s\e[m\n", ips[i]);
  }

  if (p_cnt == 0) {
    printf("|\n"
      "|  ports (%u): full\n", p_cnt);
  }

  if (p_cnt > 0) {
    printf("|\n"
      "|  ports (%u):\n", p_cnt);
    int cnt = 0;
    for (i = 0; cnt < 10 && i < 65535; i++) {
      if (ports[i]) {
        printf("|    * \e[0;34m%d\e[m\n", i);
	cnt++;
      }
    }
    if (p_cnt >= 10) {
      printf("|    * \e[0;34m...\e[m\n");
    }
  }

  printf("++++++++++++++++++++++++++++\n");
  
}

void print_usage(char* argv0) {
  printf("%s [options]\n\n"
    "mandatory:\n"
    "  -s/--source <source>        - specify the IP to recognize (multiple IPs allowed)\n"
    "\noptional:\n"
    "  -i/--interface <interface>  - specify the interface to listen to\n"
    "  -m/--mode [syn]             - specify the mode of operation - default mode is syn\n"
    "            [syn]             - recognize syn packets on closed ports and reply with syn ack (default)\n"
    "  -p/--port <port>            - specify the port to respond to (multiple ports aloowed)\n"
    "  -d/--dynamic                - make dynamic updates to the port lists based on the open/closed ports on the system\n"
    "  -v/--verbose <level>        - specify the verbosity level\n"
    "  --version                   - print the version\n"
    "\nexample:\n"
    "  *) %s -s 192.168.1.50 -s 10.0.0.12\n"
    "     Listen to SYN packets (arbitrary ports) to the IPs 192.168.1.50 or 10.0.0.12 and respond with SYN/ACK packets\n"
    "  *) %s -s 10.0.0.12 -p 22 -p 80 -p 8080\n"
    "     Listen to SYN packets to the IP 10.0.0.12 send to one of the ports [22, 80, 8080] and respond with SYN/ACK packets\n"
    "  *) %s -s 10.0.0.12 -d\n"
    "     Listen to SYN packets to the IP 10.0.0.12 send to any closed port and respond with SYN/ACK packets - keep list of closed ports up-to-date\n"
    "\n",
    argv0, argv0, argv0, argv0);
}

extern void init_callbacks();

void start_processing(u8 mode);
void cleanup();

u8* dev = NULL;

u8** ips = NULL;
u32 ips_size = 0;
u32 ips_cnt = 0;
u8 flag_dynamic = 0;

int main (int argc, char** argv) {

  // Variables for opration

  u8 mode = 0;
  log_level = 0;

  // Read all the options
  s32 c;
  s32 option_index = 0;

  if (argc <= 1) {
    print_usage(argv[0]);
    exit(1);
  }

  while ((c = getopt_long(argc, argv, "m:thi:s:v:p:d", long_options, &option_index)) > 0) {

    switch (c) {
      case 'h':
        print_usage(argv[0]);
        exit(0);
        break;
      case 'i':
        if (optarg) {
          dev = ck_alloc(strlen(optarg)+1);
          bzero(dev, strlen(optarg)+1);
          strncpy((char*)dev, optarg, strlen(optarg));
        }
        break;
      case 'm':
        if (optarg) {
          mode = atoi(optarg);
        }
        break;
      case 'd':
        flag_dynamic = 1;
        break;
      case 's':
        if (optarg) {
          if (!ips_size) {
            ips_size = 10;
            ips = ck_alloc(ips_size*sizeof(char*));
          } else {
            if (ips_size <= ips_cnt) {
              // We need a realloc
              ips_size *= 2;
              ips = realloc(ips, ips_size*sizeof(char*));
            }
          }
          ips[ips_cnt] = ck_alloc(strlen(optarg) + 1);
          bzero(ips[ips_cnt], strlen(optarg) + 1);
          strncpy((char*)ips[ips_cnt], optarg, strlen(optarg) + 1);
          ips_cnt += 1;
        }
        break;
      case 'p':
        if (optarg) {
	  u16 port = (u16)atoi(optarg);
	  if (port >= 0 && port <= 65535)
	    ports[port] = 1;
	  p_cnt++;
	}
	break;
      case 'v':
        if (optarg) {
          log_level = atoi(optarg);
	}
	break;
      case '!':
        printf("patwd - by Sebastian Roschke (bykhee@gmail.com)\n%s\n", VERSION);
	exit(0);
	break;
      case '?':
        print_usage(argv[0]);
        exit(0);
        break;
      default:
        break;
    }
  }

  if (!dev) {
    dev = (u8*)pcap_lookupdev(errbuf);
    if (!dev) {
      u8* lstr = alloc_printf("Couldn't find default device: %s\n", errbuf);
      log_error(lstr);
      ck_free(lstr);
      exit(2);
    }
  }

  if (p_cnt == 0) {
    int i = 0;
    for (i = 0; i < NUM_PORTS; i++) {
      ports[i] = 1;
    }
  }

  u8* mode_name;
  if (!mode) {
    mode = 1;
    mode_name = (u8*)"syn";
  } else {
    if (mode == 1) {
      mode_name = (u8*)"syn";
    }
  }

  // Handle pcap preparation for specific modes
  if (mode == 1) {
    syn_mode_cfg();
  }

  if (ips_cnt == 0) {
    // We could even get IPs on the fly... (TODO)
    log_error((u8*)"No IPs defined! Use -s option!\n");
    exit(2);
  }

  init_callbacks();

  // Fork the child here
  s32 pid = fork();

  if (pid < 0) {
    log_error((u8*)"Initial fork() failed!\n");
    exit(2);
  }

  // Parent process needs to handle the threading
  if (pid != 0) {

    print_header(dev, mode_name, ips, ips_cnt, ports);

    while (1) {
      s32 status;
      waitpid(pid, &status, 0);

      // If this returns, we need to restart the pcap_listener
      pid = fork();

      if (pid < 0) {
        log_error((u8*)"Initial fork() failed!\n");
        exit(2);
      }
      if (pid != 0) {
      } else {
        start_processing(mode);
        break;
      }
    }
  } else {
    start_processing(mode);
  }

  //pthread_join( thread1, NULL);

  cleanup();
  return 0;
}

void start_processing(u8 mode) {

  // To get open ports, we need to consult /proc/net/tcp(6)
  // using a thread and get results from proc - see netstat
  if (flag_dynamic) {
    u8* proc_net[] = {(u8*)"/proc/net/tcp", (u8*)"/proc/net/tcp6"};
    pthread_t thread1;
    int ret = pthread_create( &thread1, NULL, update_ports, (void*) proc_net );

    if (ret) {
      // Pthread error... crap.
      log_error((u8*)"Phtread creation failed! Can not update ports on /proc/net/tcp(6)!\n");
    }
  } 

  // Handle different modes
  if (mode == 1) {
    syn_mode_sniffing();
  }
}

void cleanup () {

  ck_free(dev);

  int i = 0;
  for (i = 0; i < ips_cnt; i++) {
    ck_free(ips[i]);
  }
  ck_free(ips);
}
